home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / WWW / Perl_WWW_Utilities / txt2html / txt2html.pl < prev   
Encoding:
Perl Script  |  1996-02-06  |  32.8 KB  |  1,291 lines

  1. : # Use perl
  2.     eval 'exec perl -S $0 "$@"'
  3.     if 0;
  4.  
  5. # It's faster to use something like #!/usr/bin/perl but you have to
  6. # know where perl is on your system.  I didn't want to have to tell
  7. # people how to do this, so I don't distribute it that way.  You
  8. # really ought to change it though.  On my machine, it saves about a
  9. # half a second.
  10.  
  11. #
  12. # txt2html.pl
  13. # Convert raw text to something with a little HTML formatting
  14. #
  15. # Written by Seth Golub <seth@cs.wustl.edu> 
  16. #            http://www.cs.wustl.edu/~seth/txt2html/
  17. #
  18. # $Revision: 1.16 $
  19. # $Date: 1996/02/06 21:26:11 $
  20. #
  21.  
  22.  
  23. #################################################################
  24. # Some initializations that need to go before the configuration
  25. #
  26.  
  27. @links_dictionaries = 0;
  28.  
  29. #
  30. #########################
  31.  
  32.  
  33. #########################
  34. # Configurable options
  35. #
  36.  
  37. # [-s <n>    ] | [--shortline <n>                 ]
  38. $short_line_length = 40;        # Lines this short (or shorter) must be
  39.                 # intentionally broken and are kept
  40.                 # that short. <BR>
  41.  
  42. # [-p <n>    ] | [--prewhite <n>                  ]
  43. $preformat_whitespace_min = 5;  # Minimum number of consecutive 
  44.                                 # whitespace characters to trigger 
  45.                                 # preformatting.  
  46.                 # NOTE: Tabs are now expanded to
  47.                 # spaces before this check is made. 
  48.                 # That means if $tab_width is 8 and
  49.                 # this is 5, then one tab may be
  50.                 # expanded to 8 spaces, which is
  51.                 # enough to trigger preformatting.
  52.  
  53. $par_indent = 2;        # Minumum number of spaces indented in 
  54.                 # first lines of paragraphs.
  55.                 #   Only used when there's no blank line
  56.                 # preceding the new paragraph.  (Like in
  57.                 # this comment)
  58.  
  59. # [-pb <n>   ] | [--prebegin <n>                  ]
  60. $preformat_trigger_lines = 2;    # How many lines of preformatted-looking
  61.                 # text are needed to switch to <PRE>
  62.                 # <= 0 : Preformat entire document
  63.                 #    1 : one line triggers
  64.                 # >= 2 : two lines trigger
  65.  
  66. # [-pe <n>   ] | [--preend <n>                    ]
  67. $endpreformat_trigger_lines = 2; # How many lines of unpreformatted-looking
  68.                  # text are needed to switch from <PRE>
  69.                                  # <= 0 : Never preformat within document
  70.                  #    1 : one line triggers
  71.                  # >= 2 : two lines trigger
  72. # NOTE for --prebegin and --preend:
  73. # A zero takes precedence.  If one is zero, the other is ignored.
  74. # If both are zero, entire document is preformatted.
  75.  
  76.  
  77. # [-r <n>    ] | [--hrule <n>                     ]
  78. $hrule_min = 4;            # Min number of ---s for an HRule.
  79.  
  80. # [-c <n>    ] | [--caps <n>                      ]
  81. $min_caps_length = 3;        # min sequential CAPS for an all-caps line
  82.  
  83. # [-ct <tag> ] | [--capstag <tag>                 ]
  84. $caps_tag = "STRONG";        # Tag to put around all-caps lines
  85.  
  86. # [-m/+m     ] | [--mail        / --nomail        ]
  87. $mailmode = 0;            # Deal with mail headers & quoted text
  88.  
  89. # [-u/+u     ] | [--unhyphenate / --nounhyphenate ]
  90. $unhyphenation = 1;        # Enables unhyphenation of text.
  91.  
  92. # [-a <file> ] | [--append <file>                 ]
  93. # [+a        ] | [--noappend                      ]
  94. $append_file = 0;        # If you want something appended by 
  95.                 # default, put the filename here.
  96.                 # The appended text will not be
  97.                 # processed at all, so make sure it's
  98.                 # plain text or decent HTML.  i.e. do
  99.                 # not have things like:
  100.                 #   Seth Golub <seth@cs.wustl.edu>
  101.                 # but instead, have:
  102.                 #   Seth Golub <seth@cs.wustl.edu>
  103.  
  104. # [-t <title>] | [--title <title>                 ]
  105. $title = 0;            # You can specify a title.
  106.                 # Otherwise it won't put one in.
  107.  
  108. # [-ul <n>   ] | [--ulength <n>             ]
  109. $underline_length_tolerance = 1; # How much longer or shorter can 
  110.                                  # underlines be and still be underlines?
  111.  
  112. # [-uo <n>   ] | [--uoffset <n>            ]
  113. $underline_offset_tolerance = 1; # How far offset can underlines 
  114.                  # be and still be underlines?
  115.  
  116. # [-tw <n>   ] | [--tabwidth <n>                  ]
  117. $tab_width = 8;            # How many spaces equal a tab?
  118.  
  119.  
  120. # [-iw <n>   ] | [--indent <n>                    ]
  121. $indent_width = 2;        # Indents this many spaces for each 
  122.                 # level of a list
  123.  
  124. # [-/+e      ] | [--extract / --noextract         ]
  125. $extract = 0;            # Extract Mode (suitable for inserting)
  126.  
  127. # [-l <file> ] | [--link <dictfile>               ]
  128. # [+l        ] | [--nolink                        ]
  129. $make_links = 1;        # Should we try to link anything?
  130.  
  131. # [-H <regexp>] | [--heading <regexp>              ]
  132. @custom_heading_regexp = ();    # Add a regexp for headings.
  133.                                 # If it contains at least one
  134.                                 # subexpression, the first one will be
  135.                                 # used as a style identifier.  A
  136.                                 # second one, if it exists, will
  137.                                 # indicate what text should be kept. 
  138.                                 # For example, "^(:+)(.*)" would match
  139.                                 # any line that started with a colon. 
  140.                                 # The style text would be the colons
  141.                                 # (however many there were), and the
  142.                                 # colons would then be removed from
  143.                                 # the line.  The styles are used to
  144.                                 # determine the heading level.  The
  145.                                 # first style found in the document is
  146.                                 # level 1, the second level 2, etc.
  147.  
  148. # Not implemented yet.
  149. # [-T <t>:<r> ] | [--tag <tagname>:<regexp>        ]
  150. @custom_tags = ();              # Similar to --heading, this lets you
  151.                                 # specify arbitrary patterns to tag. 
  152.                                 # The first subexpression, if one is
  153.                                 # present, will replace the entire
  154.                                 # matched text.  Example:
  155.                                 # "em:\*(\w+)\*" will match any word
  156.                                 # surrounded by asterisks and mark it
  157.                                 # as emphasized, removing the
  158.                                 # asterisks.
  159.  
  160. # [-db <n>   ] | [--debug <n>                      ]
  161. $dict_debug = 0;        # Debug mode for link dictionaries
  162.                 # Bitwise-Or what you want to see:
  163.                 # 1: The parsing of the dictionary
  164.                 # 2: The code that will make the links
  165.  
  166. $system_link_dict = "/usr/local/lib/txt2html-linkdict"; # after options
  167. $default_link_dict = "$ENV{'HOME'}/.txt2html-linkdict"; # before options
  168.  
  169.  
  170. # Uncomment the following lines if you want to force the heading
  171. # styles to match what Mosaic outputs.  (Underlined with "***"s is H1,
  172. # with "==="s is H2, etc.)  This was the behavior of txt2html up to
  173. # version 1.10.
  174. #
  175. #$heading_styles{"*"} = ++$num_heading_styles;
  176. #$heading_styles{"="} = ++$num_heading_styles;
  177. #$heading_styles{"+"} = ++$num_heading_styles;
  178. #$heading_styles{"-"} = ++$num_heading_styles;
  179. #$heading_styles{"~"} = ++$num_heading_styles;
  180. #$heading_styles{"."} = ++$num_heading_styles;
  181.  
  182. # END OF CONFIGURABLE OPTIONS
  183. ########################################
  184.  
  185.  
  186. ########################################
  187. # Definitions  (Don't change these)
  188. #
  189.  
  190.  
  191. # These are just constants I use for making bit vectors to keep track
  192. # of what modes I'm in and what actions I've taken on the current and
  193. # previous lines.  
  194.  
  195. $NONE       =   0;
  196. $LIST       =   1;
  197. $HRULE      =   2;
  198. $PAR        =   4;
  199. $PRE        =   8;
  200. $END        =  16;
  201. $BREAK      =  32;
  202. $HEADER     =  64;
  203. $MAILHEADER = 128;
  204. $MAILQUOTE  = 256;
  205. $CAPS       = 512;
  206. $LINK       =1024;
  207.  
  208.  
  209. # Constants for Ordered Lists and Unordered Lists.  
  210. # I use this in the list stack to keep track of what's what.
  211.  
  212. $OL = 1;
  213. $UL = 2;
  214.  
  215.  
  216. ########################################
  217. ########################################
  218. #
  219. # Subroutine definitions
  220.  
  221. sub usage
  222. {
  223.     $0 =~ s#.*/##;
  224.     print STDERR <<EOUsage;
  225.  
  226. Usage: $0 [options]
  227.  
  228. where options are:
  229.      [-v         ] | [--version                       ]
  230.      [-h         ] | [--help                          ]
  231.      [-t <title> ] | [--title <title>                 ]
  232.      [-l <file>  ] | [--link <dictfile>               ]
  233.      [+l         ] | [--nolink                        ]
  234.      [-H <regexp>] | [--heading <regexp>              ]
  235.      [-a <file>  ] | [--append <file>                 ]
  236.      [+a         ] | [--noappend                      ]
  237.      [-e/+e      ] | [--extract / --noextract         ]
  238.      [-c <n>     ] | [--caps <n>                      ]
  239.      [-ct <tag>  ] | [--capstag <tag>                 ]
  240.      [-m/+m      ] | [--mail     / --nomail           ]
  241.      [-u/+u      ] | [--unhyphen / --nounhyphen       ]
  242.      [-tw <n>    ] | [--tabwidth <n>                  ]
  243.      [-iw <n>    ] | [--indent <n>                    ]
  244.      [-ul <n>    ] | [--ulength <n>                   ]
  245.      [-uo <n>    ] | [--uoffset <n>                   ]
  246.      [-s <n>     ] | [--shortline <n>                 ]
  247.      [-p <n>     ] | [--prewhite <n>                  ]
  248.      [-pb <n>    ] | [--prebegin <n>                  ]
  249.      [-pe <n>    ] | [--preend <n>                    ]
  250.      [-r <n>     ] | [--hrule <n>                     ]
  251.      [-db <n>    ] | [--debug <n>                     ]
  252.  
  253.   More complete explanations of these options can be found in 
  254.   comments near the beginning of the script.
  255.  
  256. EOUsage
  257. #     [-T <t>:<r> ] | [--tag <tagname>:<regexp>        ]
  258. }
  259.  
  260.  
  261. sub deal_with_options
  262. {
  263.     while ($ARGV[0] =~ /^[-+].+/)
  264.     {
  265.     if (($ARGV[0] eq "-l" || $ARGV[0] eq "--link") &&
  266.         $ARGV[1])
  267.     {
  268.         if (-r $ARGV[1]) {
  269.                 $make_links = 1;
  270.                 # Stick it on the end of the list
  271.         push(@links_dictionaries, $ARGV[1]);
  272.         } else {
  273.         print STDERR "Can't find or read link-file $ARGV[1].\n";
  274.         }
  275.         shift @ARGV;
  276.         next;
  277.     }
  278.  
  279.     if (($ARGV[0] eq "+l" || $ARGV[0] eq "--nolink") )
  280.     {
  281.             $system_link_dict = "";
  282.             $make_links = 0;
  283.             @links_dictionaries = 0;
  284.         next;
  285.     }
  286.  
  287.     if (($ARGV[0] eq "-H" || $ARGV[0] eq "--heading") &&
  288.         $ARGV[1])
  289.     {
  290.             push(@custom_heading_regexp, $ARGV[1]);
  291.         shift @ARGV;
  292.         next;
  293.     }
  294.  
  295.     if (($ARGV[0] eq "-T" || $ARGV[0] eq "--tag") &&
  296.         $ARGV[1])
  297.     {
  298.             print STDERR "Sorry.  $ARGV[0] isn't supported yet.\n";
  299.             push(@custom_tags, $ARGV[1]);
  300.         shift @ARGV;
  301.         next;
  302.     }
  303.  
  304.     if (($ARGV[0] eq "-r" || $ARGV[0] eq "--hrule") &&
  305.         $ARGV[1] =~ /^\d+$/)
  306.     {
  307.         $hrule_min = $ARGV[1];
  308.         shift @ARGV;
  309.         next;
  310.     }
  311.  
  312.     if (($ARGV[0] eq "-s" || $ARGV[0] eq "--shortline") &&
  313.         $ARGV[1] =~ /^\d+$/)
  314.     {
  315.         $short_line_length = $ARGV[1];
  316.         shift @ARGV;
  317.         next;
  318.     }
  319.  
  320.     if (($ARGV[0] eq "-p" || $ARGV[0] eq "--prewhite") &&
  321.         $ARGV[1] =~ /^\d+$/)
  322.     {
  323.         $preformat_whitespace_min = $ARGV[1];
  324.         shift @ARGV;
  325.         next;
  326.     }
  327.  
  328.     if (($ARGV[0] eq "-pb" || $ARGV[0] eq "--prebegin") &&
  329.         $ARGV[1] =~ /^-?\d+$/)
  330.     {
  331.         $preformat_trigger_lines = $ARGV[1];
  332.         shift @ARGV;
  333.         next;
  334.     }
  335.     
  336.     if (($ARGV[0] eq "-pe" || $ARGV[0] eq "--preend") &&
  337.         $ARGV[1] =~ /^-?\d+$/)
  338.     {
  339.         $endpreformat_trigger_lines = $ARGV[1];
  340.         shift @ARGV;
  341.         next;
  342.     }
  343.  
  344.     if (($ARGV[0] eq "-e" || $ARGV[0] eq "--extract"))
  345.     {
  346.         $extract = 1;
  347.         next;
  348.     }
  349.  
  350.     if (($ARGV[0] eq "+e" || $ARGV[0] eq "--noextract"))
  351.     {
  352.         $extract = 0;
  353.         next;
  354.     }
  355.  
  356.     if (($ARGV[0] eq "-c" || $ARGV[0] eq "--caps") &&
  357.         $ARGV[1] =~ /^\d+$/)
  358.     {
  359.         $min_caps_length = $ARGV[1];
  360.         shift @ARGV;
  361.         next;
  362.     }
  363.  
  364.     if (($ARGV[0] eq "-ct" || $ARGV[0] eq "--capstag") &&
  365.         $ARGV[1])
  366.     {
  367.         $caps_tag = $ARGV[1];
  368.         shift @ARGV;
  369.         next;
  370.     }
  371.  
  372.     if ($ARGV[0] eq "-m" || $ARGV[0] eq "--mail")
  373.     {
  374.         $mailmode = 1;
  375.         next;
  376.     }
  377.  
  378.     if ($ARGV[0] eq "+m" || $ARGV[0] eq "--nomail")
  379.     {
  380.         $mailmode = 0;
  381.         next;
  382.     }
  383.  
  384.     if ($ARGV[0] eq "-u" || $ARGV[0] eq "--unhyphen")
  385.     {
  386.         $unhyphenation = 1;
  387.         next;
  388.     }
  389.  
  390.     if ($ARGV[0] eq "+u" || $ARGV[0] eq "--nounhyphen")
  391.     {
  392.         $unhyphenation = 0;
  393.         next;
  394.     }
  395.  
  396.     if (($ARGV[0] eq "-a" || $ARGV[0] eq "--append") &&
  397.         $ARGV[1])
  398.     {
  399.         if (-r $ARGV[1]) {
  400.         $append_file = $ARGV[1];
  401.         } else {
  402.         print STDERR "Can't find or read $ARGV[1].\n";
  403.         }
  404.         shift @ARGV;
  405.         next;
  406.     }
  407.  
  408.     if ($ARGV[0] eq "+a" || $ARGV[0] eq "--noappend")
  409.     {
  410.         $append_file = 0;
  411.         next;
  412.     }
  413.  
  414.     if (($ARGV[0] eq "-t" || $ARGV[0] eq "--title") &&
  415.         $ARGV[1])
  416.     {
  417.         $title = $ARGV[1];
  418.         shift @ARGV;
  419.         next;
  420.     }
  421.  
  422.     if (($ARGV[0] eq "-ul" || $ARGV[0] eq "--ulength") &&
  423.         $ARGV[1] =~ /^\d+$/)
  424.     {
  425.         $underline_length_tolerance = $ARGV[1];
  426.         shift @ARGV;
  427.         next;
  428.     }
  429.  
  430.     if (($ARGV[0] eq "-uo" || $ARGV[0] eq "--uoffset") &&
  431.         $ARGV[1] =~ /^\d+$/)
  432.     {
  433.         $underline_offset_tolerance = $ARGV[1];
  434.         shift @ARGV;
  435.         next;
  436.     }
  437.  
  438.     if (($ARGV[0] eq "-tw" || $ARGV[0] eq "--tabwidth") &&
  439.         $ARGV[1] =~ /^\d+$/)
  440.     {
  441.         $tab_width = $ARGV[1];
  442.         shift @ARGV;
  443.         next;
  444.     }
  445.  
  446.     if (($ARGV[0] eq "-iw" || $ARGV[0] eq "--indentwidth") &&
  447.         $ARGV[1] =~ /^\d+$/)
  448.     {
  449.         $indent_width = $ARGV[1];
  450.         shift @ARGV;
  451.         next;
  452.     }
  453.  
  454.     if ($ARGV[0] eq "-v" || $ARGV[0] eq "--version")
  455.     {
  456.         print "txt2html " . '$Revision: 1.16 $ ' . "\n";
  457.         exit;
  458.     }
  459.  
  460.     if ($ARGV[0] eq "-h" || $ARGV[0] eq "--help")
  461.     {
  462.         &usage;
  463.         exit;
  464.     }
  465.  
  466.     if (($ARGV[0] eq "-db" || $ARGV[0] eq "--debug") &&
  467.         $ARGV[1] =~ /^\d+$/)
  468.     {
  469.         $dict_debug = $ARGV[1];
  470.         shift @ARGV;
  471.         next;
  472.     }
  473.  
  474.         if ($ARGV[0] eq "--")
  475.         {
  476.             last;
  477.         }
  478.  
  479.     print STDERR "Unrecognized option: $ARGV[0]\n";
  480.     print STDERR " or bad paramater: $ARGV[1]\n" if($ARGV[1]);
  481.  
  482.     &usage;
  483.     exit(1);
  484.  
  485.     } continue {
  486.  
  487.     shift @ARGV;
  488.     }
  489.  
  490.     $preformat_trigger_lines = 0 if ($preformat_trigger_lines < 0);
  491.     $preformat_trigger_lines = 2 if ($preformat_trigger_lines > 2);
  492.  
  493.     $endpreformat_trigger_lines = 1 if ($preformat_trigger_lines == 0);
  494.     $endpreformat_trigger_lines = 0 if ($endpreformat_trigger_lines < 0);
  495.     $endpreformat_trigger_lines = 2 if ($endpreformat_trigger_lines > 2);
  496. }
  497.  
  498. sub is_blank
  499. {
  500.     return $_[0] =~ /^\s*$/;
  501. }
  502.  
  503. sub escape
  504. {
  505.     $line =~ s/&/&/g;
  506.     $line =~ s/>/>/g;
  507.     $line =~ s/</</g;
  508. }
  509.  
  510. sub hrule
  511. {
  512.     if ($line =~ /^\s*([-_~=\*]\s*){$hrule_min,}$/)
  513.     {
  514.     $line = "<HR>\n";
  515.     $prev =~ s/<P>//;
  516.     $line_action |= $HRULE;
  517.     } elsif ($line =~ /\014/)
  518.     {
  519.     $line_action |= $HRULE;
  520.         $line =~ s/\014/\n<HR>\n/g; # Linefeeds become horizontal rules
  521.     }
  522. }
  523.  
  524. sub shortline
  525. {
  526.  
  527.     # Short lines should be broken even on list item lines iff the
  528.     # following line is more text.  I haven't figured out how to do
  529.     # that yet.  For now, I'll just not break on short lines in lists.
  530.     # (sorry)
  531.  
  532.     if (!($mode & ($PRE | $LIST))
  533.     && !&is_blank($line)
  534.     && !&is_blank($prev) 
  535.     && ($prev_line_length < $short_line_length) 
  536.     && !($line_action & ($END | $HEADER | $HRULE | $LIST | $PAR))
  537.     && !($prev_action & ($HEADER | $HRULE | $BREAK)))
  538.     {
  539.     $prev .= "<BR>" . chop($prev);
  540.         $prev_action |= $BREAK;
  541.     }
  542. }
  543.  
  544. sub mailstuff
  545. {
  546.     if ((($line =~ /^\w*>/)    # Handle "FF> Werewolves."
  547.          || ($line =~ /^\w*\|/)) # Handle "Igor| There wolves."
  548.         && !&is_blank($nextline))
  549.     {
  550.     $line =~ s/$/<BR>/;
  551.     $line_action |= ($BREAK | $MAILQUOTE);
  552.         if(!($prev_action & ($BREAK | $PAR)))
  553.         {
  554.             $prev .= "<P>\n";
  555.             $line_action |= $PAR;
  556.         }
  557.     } elsif (($line =~ /^(From:?)|(Newsgroups:) /)
  558.              && &is_blank($prev))
  559.     {
  560.     &anchor_mail if !($prev_action & $MAILHEADER);
  561.         chop $line;
  562.     $line = "<!-- New Message -->\n<p>\n" . $line . "<BR>\n";        
  563.     $line_action |= ($BREAK | $MAILHEADER | $PAR);
  564.     } elsif (($line =~ /^[\w\-]*:/)  # Handle "Some-Header: blah"
  565.          && ($prev_action & $MAILHEADER) 
  566.          && !&is_blank($nextline))
  567.     {
  568.     $line =~ s/$/<BR>/;
  569.     $line_action |= ($BREAK | $MAILHEADER);
  570.     } elsif (($line =~ /^\s+\S/) &&   # Handle multi-line mail headers
  571.          ($prev_action & $MAILHEADER) &&
  572.          !&is_blank($nextline))
  573.     {
  574.     $line =~ s/$/<BR>/;
  575.     $line_action |= ($BREAK | $MAILHEADER);
  576.     }
  577. }
  578.  
  579. # Subtracts modes listed in $mask from $vector.
  580. sub subtract_modes
  581. {
  582.     local($vector, $mask) = @_;
  583.     ($vector | $mask) - $mask;
  584. }
  585.  
  586. sub paragraph
  587. {
  588.     if(!&is_blank($line)
  589.        && !($mode & $PRE)
  590.        && !&subtract_modes($line_action, $END | $MAILQUOTE | $CAPS | $BREAK)
  591.        && (&is_blank($prev) 
  592.        || ($line_action & $END)
  593.        || ($line_indent > $prev_indent + $par_indent)))
  594.     {
  595.     $prev .= "<P>\n";
  596.     $line_action |= $PAR;
  597.     }
  598. }
  599.  
  600. # If the line is blank, return the second argument.  Otherwise,
  601. # return the number of spaces before any nonspaces on the line.
  602. sub count_indent
  603. {
  604.     local($line, $prev_length) = @_;
  605.     if(&is_blank($line))
  606.     {
  607.     return $prev_length;
  608.     }
  609.     local($ws) = $line =~ /^( *)[^ ]/;
  610.     length($ws);
  611. }
  612.  
  613. sub listprefix
  614. {
  615.     local($line) = @_;
  616.     local($prefix, $number, $rawprefix);
  617.  
  618.     return (0,0,0) if (!($line =~ /^\s*[-=\*o]+\s+\S/ ) &&
  619.                !($line =~ /^\s*(\d+|[a-zA-Z])[\.\)\]:]\s+\S/ ));
  620.  
  621.     ($number) = $line =~ /^\s*(\d+|[a-zA-Z])/;
  622.  
  623.     # That slippery exception of "o" as a bullet
  624.     # (This ought to be determined using the context of what lists
  625.     #  we have in progress, but this will probably work well enough.)
  626.     if($line =~ /^\s*o\s/)
  627.     {
  628.     $number = 0;
  629.     }
  630.  
  631.     if ($number)
  632.     {
  633.     ($rawprefix) = $line =~ /^(\s*(\d+|[a-zA-Z]).)/;
  634.     $prefix = $rawprefix;
  635.     $prefix =~ s/(\d+|[a-zA-Z])//;    # Take the number out
  636.     } else {
  637.     ($rawprefix) = $line =~ /^(\s*[-=o\*]+.)/;
  638.     $prefix = $rawprefix;
  639.     }
  640.     ($prefix, $number, $rawprefix);
  641. }
  642.  
  643. sub startlist
  644. {
  645.     local($prefix, $number, $rawprefix) = @_;
  646.  
  647.     $listprefix[$listnum] = $prefix;
  648.     if($number)
  649.     {
  650.     # It doesn't start with 1,a,A.  Let's not screw with it.
  651.     if (($number != 1) && ($number ne "a") && ($number ne "A"))
  652.     {
  653.         return 0;
  654.     }
  655.     $prev .= "$list_indent<OL>\n";
  656.     $list[$listnum] = $OL;
  657.     } else {
  658.     $prev .= "$list_indent<UL>\n";
  659.     $list[$listnum] = $UL;
  660.     }
  661.  
  662.     $listnum++;
  663.     $list_indent = " " x $listnum x $indent_width;
  664.     $line_action |= $LIST;
  665.     $mode |= $LIST;
  666.     1;
  667. }
  668.  
  669.  
  670. sub endlist            # End N lists
  671. {
  672.     local($n) = @_;
  673.     for(; $n > 0; $n--, $listnum--)
  674.     {
  675.     $list_indent = " " x ($listnum-1) x $indent_width;
  676.     if($list[$listnum-1] == $UL)
  677.     {
  678.         $prev .= "$list_indent</UL>\n";
  679.     } elsif($list[$listnum-1] == $OL)
  680.     {
  681.         $prev .= "$list_indent</OL>\n";
  682.     } else
  683.     {
  684.         print STDERR "Encountered list of unknown type\n";
  685.     }
  686.     }
  687.     $line_action |= $END;
  688.     $mode ^= ($LIST & $mode) if (!$listnum);
  689. }
  690.  
  691. sub continuelist
  692. {
  693.     $line =~ s/^\s*[-=o\*]+\s*/$list_indent<LI> / if $list[$listnum-1] == $UL;
  694.     $line =~ s/^\s*(\d+|[a-zA-Z]).\s*/$list_indent<LI> /    
  695.         if $list[$listnum-1] == $OL;
  696.     $line_action |= $LIST;
  697. }
  698.  
  699. sub liststuff
  700. {
  701.     local($i);
  702.  
  703.     local($prefix, $number, $rawprefix) = &listprefix($line);
  704.  
  705.     $i = $listnum;
  706.     if (!$prefix)
  707.     {
  708.     return if !&is_blank($prev); # inside a list item
  709.  
  710.     # This ain't no list.  We'll want to end all of them.
  711.     return if !($mode & $LIST);    # This just speeds up the inevitable
  712.     $i = 0;
  713.     } else 
  714.     {
  715.     # Maybe we're going back up to a previous list
  716.     $i-- while (($prefix ne $listprefix[$i-1]) && ($i >= 0));
  717.     }
  718.  
  719.     local($islist);
  720.  
  721.     # Measure the indent from where the text starts, not where the
  722.     # prefix starts.  This won't screw anything up, and if we don't do
  723.     # it, the next line might appear to be indented relative to this
  724.     # line, and get tagged as a new paragraph.
  725.     local($total_prefix) = $line =~ /^(\s*[\da-zA-Z-=o\*]+.\s*)/;
  726.     # Of course, we only use it if it really turns out to be a list.
  727.  
  728.     $islist = 1;
  729.     if (($i >= 0) && ($i != $listnum))
  730.     { 
  731.     &endlist($listnum - $i);
  732.         $islist = 0;
  733.     } elsif (!$listnum || $i != $listnum)
  734.     { 
  735.         if ( ($line_indent > 0) || &is_blank($prev) )
  736.         {
  737.             $islist = &startlist($prefix, $number, $rawprefix);
  738.         } else 
  739.         {
  740.             # We have something like this: "- foo" which usually
  741.             # turns out not to be a list.
  742.             return;
  743.         }
  744.     }
  745.  
  746.     &continuelist($prefix, $number, $rawprefix) if ($mode & $LIST);
  747.     $line_indent = length($total_prefix) if $islist;
  748. }
  749.  
  750. # Returns true if the passed string is considered to be preformatted
  751. sub is_preformatted
  752. {
  753.     ((@_[0] =~ /\s{$preformat_whitespace_min,}\S+/o) # whitespaces
  754.      || (@_[0] =~ /\.{$preformat_whitespace_min,}\S+/o)); # dots
  755. }
  756.  
  757. sub endpreformat
  758. {
  759.     if(!&is_preformatted($line) 
  760.        && ($endpreformat_trigger_lines == 1 
  761.            || !&is_preformatted($nextline)))
  762.     {
  763.     $prev .= "</PRE>\n";
  764.     $mode ^= ($PRE & $mode);
  765.     $line_action |= $END;
  766.     }
  767. }
  768.  
  769. sub preformat
  770. {
  771.     if($preformat_trigger_lines == 0 
  772.        || (&is_preformatted($line) &&
  773.            ($preformat_trigger_lines == 1 || &is_preformatted($nextline))))
  774.     {
  775.     $line =~ s/^/<PRE>\n/;
  776.     $prev =~ s/<P>//;
  777.     $mode |= $PRE;
  778.     $line_action |= $PRE;
  779.     }
  780. }
  781.  
  782. sub make_new_anchor
  783. {
  784.     local($anchor, $i);
  785.  
  786.     return sprintf("%d", $non_header_anchor++) if(!$heading_level);
  787.     $anchor = "section-";
  788.     $heading_count[$heading_level-1]++;
  789.  
  790.     # Reset lower order counters
  791.     for($i=$#heading_count + 1; $i > $heading_level; $i--)
  792.     {
  793.         $heading_count[$i-1] = 0;
  794.     }
  795.  
  796.     for($i=0; $i < $heading_level; $i++)
  797.     {
  798.         $heading_count[$i] = 1 if !$heading_count[$i]; # In case they skip any
  799.         $anchor .= sprintf("%d.", $heading_count[$i]);
  800.     }
  801.     chop($anchor);
  802.     $anchor;
  803. }
  804.  
  805. sub anchor_mail
  806. {
  807.     local($text) = $line =~ /\S+: *(.*) *$/;
  808.     local($anchor) = &make_new_anchor($text);
  809.     $line =~ s/(.*)/<A NAME="$anchor">$1<\/A>/;
  810. }
  811.  
  812. sub anchor_heading
  813. {
  814.     local($heading) = @_;
  815.     local($anchor) = &make_new_anchor($heading);
  816.     $line =~ s/(<H.>.*<\/H.>)/<A NAME="$anchor">$1<\/A>/;
  817. }
  818.  
  819. sub heading_level
  820. {
  821.     local($style) = @_;
  822.     $heading_styles{$style} = ++$num_heading_styles
  823.         if !$heading_styles{$style};
  824.     $heading_styles{$style};
  825. }
  826.  
  827. sub heading
  828. {
  829.     local($hoffset, $heading) = $line =~ /^(\s*)(.+)$/;
  830.     local($uoffset, $underline) = $nextline =~ /^(\s*)(\S+)\s*$/;
  831.  
  832.     local($lendiff, $offsetdiff);
  833.     $lendiff = length($heading) - length($underline);
  834.     $lendiff *= -1 if $lendiff < 0;
  835.  
  836.     $offsetdiff = length($hoffset) - length($uoffset);
  837.     $offsetdiff *= -1 if $offsetdiff < 0;
  838.  
  839.     if(&is_blank($line)
  840.        ||($lendiff > $underline_length_tolerance)
  841.        ||($offsetdiff > $underline_offset_tolerance))
  842.     {
  843.     return;
  844.     }
  845.  
  846.     $underline = substr($underline,0,1);
  847.  
  848.     $underline .= "C" if &iscaps($line); # Call it a different style if the
  849.                                          # heading is in all caps.
  850.     $nextline = &getline;             # Eat the underline
  851.     $heading_level = &heading_level($underline);
  852.     &tagline("H" . $heading_level);
  853.     &anchor_heading($heading);
  854.     $line_action |= $HEADER;
  855. }
  856.  
  857. sub custom_heading
  858. {
  859.     local($i);
  860.     for($i=0; $i <= $#custom_heading_regexp; $i++)
  861.     {
  862.         if ($line =~ /$custom_heading_regexp[$i]/)
  863.         {
  864.             ($style, $rest) = ($line =~ /$custom_heading_regexp[$i]/);
  865.             $line = ($rest . "\n") if $rest;
  866.             $style || ($style = $i);
  867.             &tagline("H" . &heading_level("Cust" . $style));
  868.             &anchor_heading($rest);
  869.             $line_action |= $HEADER;
  870.             last;
  871.         }
  872.     }
  873. }
  874.  
  875. sub unhyphenate
  876. {
  877.     local($second);
  878.  
  879.     # This looks hairy because of all the quoted characters.
  880.     # All I'm doing is pulling out the word that begins the next line.
  881.     # Along with it, I pull out any punctuation that follows.
  882.     # Preceding whitespace is preserved.  We don't want to screw up
  883.     # our own guessing systems that rely on indentation.
  884.     ($second) = $nextline =~ /^\s*([a-zA-Z]+[\)\}\]\.,:;\'\"\>]*\s*)/; # "
  885.     $nextline =~ s/^(\s*)[a-zA-Z]+[\)\}\]\.,:;\'\"\>]*\s*/$1/; # "
  886.     # (The silly comments are for my less-than-perfect code hilighter)
  887.  
  888.     $line =~ s/\-\s*$/$second/;
  889.     $line .= "\n";
  890. }
  891.  
  892. sub untabify
  893. {
  894.     local($line) = @_;
  895.     while($line =~ /\011/)
  896.     {
  897.         $line =~ s/\011/" " x ($tab_width - (length($`) % $tab_width))/e;
  898.     }
  899.     $line;
  900. }
  901.  
  902. sub tagline
  903. {
  904.     local($tag) = @_;
  905.     chop $line;                 # Drop newline
  906.     $line =~ s/^\s*(.*)$/<$tag>$1<\/$tag>\n/;
  907. }
  908.  
  909. sub iscaps
  910. {
  911.     local($_) = @_;
  912.     /^[^a-z<]*[A-Z]{$min_caps_length,}[^a-z<]*$/;
  913. }
  914.  
  915. sub caps
  916. {
  917.     if(&iscaps($line))
  918.     {
  919.     &tagline($caps_tag);
  920.     $line_action |= $CAPS;
  921.     }
  922. }
  923.  
  924. # Convert very simple globs to regexps
  925. sub glob2regexp
  926. {
  927.     local($glob) = @_;
  928.     # Escape funky chars
  929.     $glob =~ s/[^\w\[\]\*\?\|\\]/\\$&/g;
  930.     local($regexp,$i,$len,$escaped) = ("",0,length($glob),0);
  931.     
  932.     for(;$i < $len; $i++)
  933.     {
  934.     $char = substr($glob,$i,1);
  935.     if($escaped)
  936.     {
  937.         $escaped = 0;
  938.         $regexp .= $char;
  939.         next;
  940.     }
  941.     if ($char eq "\\") {
  942.         $escaped = 1; next;
  943.         $regexp .= $char;
  944.     }
  945.     if ($char eq "?") {
  946.         $regexp .= "."; next;
  947.     }
  948.     if ($char eq "*") {
  949.         $regexp .= ".*"; next;
  950.     }
  951.     $regexp .= $char;    # Normal character
  952.     }
  953.     "\\b" . $regexp . "\\b";
  954. }
  955.  
  956. sub add_regexp_to_links_table
  957. {
  958.     local($key,$URL,$switches) = @_;
  959.     # No sense adding a second one if it's already in there.
  960.     # It would never get used.
  961.     if(!$links_table{$key})
  962.     {
  963.         # Keep track of the order they were added so we can
  964.         # look for matches in the same order
  965.         push(@links_table_order, ($key));
  966.  
  967.         $links_table{$key} = $URL;        # Put it in The Table
  968.         $links_switch_table{$key} = $switches;
  969.         print STDERR 
  970.  " ($#links_table_order)\tKEY: $key\n\tVALUE: $URL\n\tSWITCHES: $switches\n\n"
  971.         if ($dict_debug & 1);
  972.     } else 
  973.     {
  974.         if($dict_debug & 1) {
  975.         print STDERR " Skipping entry.  Key already in table.\n";
  976.         print STDERR "\tKEY: $key\n\tVALUE: $URL\n\n";
  977.         }
  978.     }
  979. }
  980.  
  981. sub add_literal_to_links_table
  982. {
  983.     local($key,$URL,$switches) = @_;
  984.     $key =~ s/(\W)/\\\1/g; # Escape non-alphanumeric chars
  985.     $key = "\\b$key\\b"; # Make a regexp out of it
  986.     &add_regexp_to_links_table($key,$URL,$switches);
  987. }
  988.  
  989. sub add_glob_to_links_table
  990. {
  991.     local($key,$URL,$switches) = @_;
  992.     &add_regexp_to_links_table(&glob2regexp($key),$URL,$switches);    
  993. }
  994.  
  995. # This is the only function you should need to change if you want to
  996. # use a different dictionary file format.
  997. sub parse_dict
  998. {
  999.     local($dictfile, $dict) = @_;
  1000.  
  1001.     print STDERR "Parsing dictionary file $dictfile\n" if ($dict_debug & 1);
  1002.  
  1003.     $dict =~ s/^\#.*$//g;     # Strip lines that start with '#'
  1004.     $dict =~ s/^.*[^\\]:\s*$//g; # Strip lines that end with unescaped ':'
  1005.  
  1006.     if($dict =~ /->\s*->/)
  1007.     {
  1008.     $message = "Two consecutive '->'s found in $dictfile\n";
  1009.  
  1010.     # Print out any useful context so they can find it.
  1011.     ($near) = $dict =~ /([\S ]*\s*->\s*->\s*\S*)/;
  1012.     $message .= "\n$near\n" if $near =~ /\S/; 
  1013.     die $message;
  1014.     }
  1015.  
  1016.     while($dict =~ /\s*(.+)\s+\-+([ieh]+\-+)?\>\s*(.*\S+)\s*\n/ig)
  1017.     {
  1018.     local($key, $URL,$switches,$options);
  1019.     $key = $1;
  1020.     $options = $2;
  1021.     $URL = $3;
  1022.     $switches = 0;
  1023.     $switches += 1 if $options =~ /i/i; # Case insensitivity
  1024.     $switches += 2 if $options =~ /e/i; # This could be interesting
  1025.     $switches += 4 if $options =~ /h/i; # provides HTML, not just URL
  1026.  
  1027.     $key =~ s/\s*$//;    # Chop trailing whitespace
  1028.  
  1029.     if($key =~ m|^/|)    # Regexp
  1030.     {
  1031.         $key = substr($key,1);
  1032.         $key =~ s|/$||;    # Allow them to forget the closing /
  1033.         &add_regexp_to_links_table($key,$URL,$switches);
  1034.     } elsif($key =~ /^\|/)    # alternate regexp format
  1035.     {
  1036.         $key = substr($key,1);
  1037.         $key =~ s/\|$//;    # Allow them to forget the closing |
  1038.         $key =~ s|/|\\/|g;    # Escape all slashes
  1039.         &add_regexp_to_links_table($key,$URL,$switches);
  1040.     } elsif ($key =~ /\"/)
  1041.     {
  1042.         $key = substr($key,1);
  1043.         $key =~ s/\"$//;    # Allow them to forget the closing "
  1044.         &add_literal_to_links_table($key,$URL,$switches);
  1045.     } else
  1046.     {
  1047.         &add_glob_to_links_table($key,$URL,$switches);
  1048.     }
  1049.     }
  1050. }
  1051.  
  1052. sub in_link_context
  1053. {
  1054.     local($match, $before) = @_;
  1055.     return 1 if $match =~ m@</?A>@i; # No links allowed inside match
  1056.  
  1057.     local($final_open, $final_close);
  1058.     $final_open = rindex($before, "<A ") - $[;
  1059.     $final_close = rindex($before, "</A>") - $[;
  1060.  
  1061.     ($final_open >= 0)        # Link opened 
  1062.     && (($final_close < 0)    # and not closed    or
  1063.         || ($final_open > $final_close)); # one opened after last close
  1064. }
  1065.  
  1066. # This subroutine looks a little odd.  Rather than build up some code
  1067. # and keep "eval"ing later, I'm building a new subroutine.  This way I
  1068. # can declare local vars and not worry about the namespace in the
  1069. # calling context.  I don't know how much it really gains me, but I
  1070. # don't know of any real costs and it seems like it could be
  1071. # friendlier to optimization.  (Plus it's cool to define new
  1072. # subroutines at runtime.  :-)
  1073. sub make_dictionary_links_code
  1074. {
  1075.     local($i,$pattern,$switches,$options,$code,$href);
  1076.     $code = <<EOCode;
  1077. sub dynamic_make_dictionary_links
  1078. {
  1079.     local(\$line_link) = (\$line_action | \$LINK);
  1080.     local(\$before,\$linkme,\$line_with_links);
  1081. EOCode
  1082.     for($i=1; $i <= $#links_table_order; $i++)
  1083.     {
  1084.     $pattern = $links_table_order[$i];
  1085.     $key = $pattern;
  1086.     $switches = $links_switch_table{$key};
  1087.     
  1088.     $s_sw = "";        # Options for searching
  1089.     $s_sw .= "i" if($switches & 1);
  1090.     
  1091.     $r_sw = "";        # Options for replacing
  1092.     $r_sw .= "i" if($switches & 1);
  1093.     $r_sw .= "e" if($switches & 2);
  1094.  
  1095.     $href = $links_table{$key};
  1096.  
  1097.     $href =~ s@/@\\/@g;
  1098.     $href = '<A HREF="' . $href . '">$&<\\/A>'
  1099.         if !($switches & 4);
  1100.  
  1101.     $code .= <<EOCode;
  1102.  
  1103.     \$line_with_links = "";
  1104.     while(\$line =~ /$pattern/$s_sw)
  1105.     {
  1106.     \$link_line = $LINK if(!\$link_line);
  1107.     \$before = \$\`;
  1108.     \$linkme = \$&;
  1109.  
  1110.     \$line = substr(\$line, length(\$before) + length(\$linkme));
  1111.     \$linkme =~ s/$pattern/$href/$r_sw
  1112.         if(!&in_link_context(\$linkme,\$line_with_links . \$before));
  1113.     \$line_with_links .= \$before . \$linkme;
  1114.     }
  1115.     \$line = \$line_with_links . \$line;
  1116. EOCode
  1117.     }
  1118.     $code .= <<EOCode;
  1119.  
  1120.     \$line_action |= \$line_link; # Cheaper to only to do bitwise OR once.
  1121. }
  1122. EOCode
  1123.     print STDERR "$code" if ($dict_debug & 2);
  1124.     eval "$code";
  1125.     if($@)
  1126.     {
  1127.         print STDERR "Problem making dictionary eval code\n";
  1128.         die $@;
  1129.     }
  1130. }
  1131.  
  1132. sub load_dictionary_links
  1133. {
  1134.     local($dict, $contents);
  1135.     local($i);
  1136.     @links_table_order = 0;
  1137.     %links_table = 0;
  1138.     for($i=1; $i <= $#links_dictionaries; $i++)
  1139.     {
  1140.     $dict= $links_dictionaries[$i];
  1141.  
  1142.     (-r "$dict") || die "Dictionary file $dict not found or unreadable";
  1143.     open(DICT, "$dict") || die "Can't open Dictionary file $dict";
  1144.  
  1145.     $contents = "";
  1146.     $contents .= $_ while(<DICT>);
  1147.     close(DICT);
  1148.     &parse_dict($dict, $contents);
  1149.     }
  1150.     &make_dictionary_links_code;
  1151. }
  1152.  
  1153. sub make_dictionary_links
  1154. {
  1155.     eval "&dynamic_make_dictionary_links;";
  1156.     warn $@ if $@;
  1157. }
  1158.  
  1159. sub getline
  1160. {
  1161.     local($line);
  1162.     $line = <>;
  1163.     $line =~ s/[ \011]*\015$//;    # Chop trailing whitespace and DOS CRs
  1164.     $line = &untabify($line);   # Change all tabs to spaces
  1165.     $line;
  1166. }
  1167.  
  1168. sub main
  1169. {
  1170.     $* = 1;            # Turn on multiline searches
  1171.     push(@links_dictionaries,($default_link_dict)) 
  1172.         if ($make_links && (-f $default_link_dict));
  1173.     &deal_with_options;
  1174.     if($make_links)
  1175.     {
  1176.         push(@links_dictionaries,($system_link_dict)) if -f $system_link_dict;
  1177.         &load_dictionary_links;
  1178.     }
  1179.     if(!$extract)
  1180.     {
  1181.     print "<HTML>\n";
  1182.     print "<HEAD>\n";
  1183.  
  1184.     # It'd be nice if we could guess a title from the first header,
  1185.     # but even that would be too late if we're doing this in one pass.
  1186.     print "<TITLE>$title</TITLE>\n" if($title);
  1187.  
  1188.     print "</HEAD>\n";
  1189.                 
  1190.     print "<BODY>\n";
  1191.     }
  1192.  
  1193.     $prev_line_length = 0;
  1194.     $prev_indent = 0;
  1195.     $prev     = "";
  1196.     $line     = &getline;
  1197.     $nextline = &getline if $line;
  1198.     $line = &untabify($line);
  1199.     do 
  1200.     {
  1201.     $line_length = length($line); # Do this before tags go in
  1202.     $line_indent = &count_indent($line, $prev_indent);
  1203.  
  1204.     &escape;
  1205.  
  1206.     &endpreformat if (($mode & $PRE) && ($preformat_trigger_lines != 0));
  1207.  
  1208.     &hrule if !($mode & $PRE);
  1209.  
  1210.     &liststuff if (!($mode & $PRE) && 
  1211.                !&is_blank($line));
  1212.  
  1213.         &custom_heading if (($#custom_heading_regexp > -1)
  1214.                             && !($mode & $PRE));
  1215.  
  1216.     &heading   if (!($mode & ($PRE | $HEADER)) && 
  1217.                $nextline =~ /^\s*[=\-\*\.~\+]+\s*$/);
  1218.  
  1219. #        &custom_tag if (($#custom_tags > -1)
  1220. #                        && !($mode & $PRE)
  1221. #                        && !($line_action & $HEADER));
  1222.  
  1223.     &mailstuff if ($mailmode && 
  1224.                !($mode & $PRE) && 
  1225.                !($line_action & $HEADER));
  1226.  
  1227.     &preformat if (!($line_action & ($HEADER | $LIST | $MAILHEADER)) && 
  1228.                !($mode & ($LIST | $PRE)) &&
  1229.                ($endpreformat_trigger_lines != 0));
  1230.  
  1231.     ¶graph;
  1232.     &shortline;
  1233.  
  1234.     &unhyphenate if ($unhyphenation && 
  1235.              ($line =~ /[a-zA-Z]\-$/) && # ends in hyphen
  1236.              # next line starts w/letters
  1237.              ($nextline =~ /^\s*[a-zA-Z]/) && 
  1238.              !($mode & ($PRE | $HEADER | $MAILHEADER | $BREAK)));
  1239.  
  1240.     &make_dictionary_links if ($make_links
  1241.                                    && !&is_blank($line)
  1242.                    && $#links_table_order);
  1243.  
  1244.     &caps if  !($mode & $PRE);
  1245.  
  1246.     # Print it out and move on.
  1247.  
  1248.     print $prev;
  1249.  
  1250.     if (!&is_blank($nextline))
  1251.     {
  1252.         $prev_action = $line_action;
  1253.         $line_action     = $NONE;
  1254.         $prev_line_length = $line_length;
  1255.         $prev_indent = $line_indent;
  1256.     }
  1257.  
  1258.     $prev = $line;
  1259.     $line = $nextline;
  1260.     $nextline = &getline if $nextline;
  1261.     } until (!$nextline && !$line && !$prev);
  1262.  
  1263.     $prev = "";
  1264.     &endlist($listnum) if ($mode & $LIST); # End all lists
  1265.     print $prev;
  1266.  
  1267.     print "\n";
  1268.  
  1269.     print "</PRE>\n" if ($mode & $PRE);
  1270.  
  1271.     if ($append_file)
  1272.     {
  1273.     if(-r $append_file)
  1274.     {
  1275.         open(APPEND, $append_file);
  1276.         print while <APPEND>;
  1277.     } else {
  1278.         print STDERR "Can't find or read file $append_file to append.\n";
  1279.     }
  1280.     }
  1281.  
  1282.     if(!$extract)
  1283.     {
  1284.     print "</BODY>\n";
  1285.     print "</HTML>\n";
  1286.     }
  1287. }
  1288.  
  1289. &main();
  1290.  
  1291.